/** \file gr_tri_mesh.h A triangle mesh for storage of large meshes. 
    Using the Campagna '98 "Directed Edges - A Scalable Representation for Triangle Meshes"
    Directed Edge data structure.

    Since this is used also for rendering support is added for auxiliary channels of vertex data.

*/

#pragma once

#include "gr_numeric_types.h"
#include "gr_aligned_allocator.h"
#include "gr_streamsignature.h"
#include "gr_streamdata.h"

#include "gr_tokens.h" // We use default names GR_POSITION and GR_NORMAL to identiy position and normal channels
#include "gr_shims.h"
#include "gr_algorithms.h"
#include "gr_containers.h"

#include<vector>
#include<functional>
#include<stdint.h>
#include<cstring>
#include<exception>
#include<memory>
#include<unordered_map>
#include<set>

namespace gr {

/** Namespace container for common mesh properties*/
class Mesh
{
    public:
    enum AttributeHost {VERTEX, EDGE, FACE};
    enum AttributeType {MESH_VEC4, MESH_VEC3, MESH_FLOAT}; // TODO ->, MESH_INT32, MESH_STRING};

    typedef AlignedArray<vec4> Vec4Container; // need a 16-byte aligned container
    typedef std::vector<vec3>  Vec3Container;
    typedef std::vector<float> FloatContainer;
    
    //typedef std::vector<int32_t>     Int32Container;
    //typedef std::vector<std::string> StringContainer;

    static const std::string mesh__dummy__string;

    struct AnnotatedView
    {
        std::reference_wrapper<const std::string> name;
        AttributeHost host;
        AttributeType type;
        UnsafeView    view;
        AnnotatedView(const std::string& viewName, AttributeHost attributeHost, AttributeType attributeType, UnsafeView unsafeView):
            name(viewName), host(attributeHost), type(attributeType), view(unsafeView){}
        AnnotatedView():name(mesh__dummy__string){}
        AnnotatedView& operator=(const AnnotatedView& other)
        {
            name = other.name;
            host = other.host;
            type = other.type;
            view = other.view;
            return *this;
        }
    };

    static AnnotatedView annotatedView(const std::string& name, AttributeHost host, AttributeType type, UnsafeView view)
    {
        return AnnotatedView(name, host, type, view);
    }

    /** Use attributes to attach properties to mesh. 
     * Specialize this template type
     * */
    template<class M>
    class AttributeHandle
    {
    public:
        typedef std::function<void(AttributeHandle<M>&, M&)> UpdateFun;

        Vec4Container* getAttributeArrayVec4() const
        {
            Vec4Container* arr = nullptr;
            if(type_ == MESH_VEC4) arr = reinterpret_cast<Vec4Container*>(container_);
            else throw std::runtime_error("Mesh::Attribute incorrect data type access: not Vec4.");
            return arr;
         }

        Vec3Container* getAttributeArrayVec3() const
        {
            Vec3Container* arr = nullptr;
            if(type_ == MESH_VEC3) arr = reinterpret_cast<Vec3Container*>(container_); 
            else throw std::runtime_error("Mesh::Attribute incorrect data type access: not Vec3.");
            return arr;
        }

        FloatContainer* getAttributeArrayFloat() const
        {
            FloatContainer* arr = nullptr;
            if(type_ == MESH_FLOAT) arr = reinterpret_cast<FloatContainer*>(container_);
            else throw std::runtime_error("Mesh::Attribute incorrect data type access: not float.");
            return arr;
        }

        static void emptyUpdate(AttributeHandle<M>& attribute, M& mesh)
        {
            return;
        }


        AttributeHandle(const char* aname, AttributeHost ahost, AttributeType atype, UpdateFun afun)
            :name_(aname), host_(ahost), type_(atype),container_(nullptr), attached_(false), updateFun_(afun), size_(0)
        {}

        AttributeHandle(const char* aname, AttributeHost ahost, AttributeType atype)
            :name_(aname), host(ahost), type_(atype),container_(nullptr),
             attached_(false), updateFun_(emptyUpdate), size_(0)
        {}

    private:
        void pushAttributeVec4()
        {
            auto attr_array = getAttributeArrayVec4();
            attr_array.push_back(vec4(1.0f, 0.0f, 0.0f, 1.0f));
        }

        void pushAttributeVec3()
        {
           auto attr_array = getAttributeArrayVec3();
           attr_array.push_back(vec3(1.0f, 0.0f, 0.0f));
        }

        void pushAttributeFloat()
        {
           auto attr_array = getAttributeArrayFloat();
           attr_array.push_back(0.0f);
        }
        void resizeAttributeVec4(size_t newSize)
        {
            auto attr_array = getAttributeArrayVec4();
            attr_array.resize(newSize,vec4(1.0f, 0.0f, 0.0f, 1.0f));
        }

        void resizeAttributeVec3(size_t newSize)
        {
           auto attr_array = getAttributeArrayVec3();
           attr_array.resize(newSize, vec3(1.0f, 0.0f, 0.0f));
        }

        void resizeAttributeFloat(size_t newSize)
        {
           auto attr_array = getAttributeArrayFloat();
           attr_array.resize(newSize,0.0f);
        }
    public:
        void pushAttribute()
        {
            switch(type_)
            {
                case MESH_FLOAT: pushAttributeFloat(); break;
                case MESH_VEC3: pushAttributeVec3(); break;
                case MESH_VEC4: pushAttributeVec4(); break;
            }
            size_ += 1;
        }

        void resizeTo(size_t s)
        {
            if(s > size_)
            {
                size_t count = s - size_;
                switch(type_)
                {
                    case MESH_FLOAT: resizeAttributeFloat(); break;
                    case MESH_VEC3:  resizeAttributeVec3(); break;
                    case MESH_VEC4:  resizeAttributeVec4(); break;
                }
            }
        }

        void resizeToLinkedElementCount(M& mesh)
        {
            switch(host_)
            {
                case VERTEX: resizeTo(mesh.vertexCount()); break;
                case FACE:   resizeTo(mesh.faceCount()); break;
                case EDGE:   resizeTo(mesh.edgeCount()); break;
            }
        }

        void attach(){
            switch(type_)
            {
                case MESH_VEC4:container_ = (void*) new Vec4Container(); break;
                case MESH_VEC3:container_ = (void*) new Vec3Container(); break;
                case MESH_FLOAT:container_ =(void*) new FloatContainer(); break;
                default: break;
            }
            attached_ = true;
            if(! updateFun_) updateFun_ = emptyUpdate;
        }

        ~AttributeHandle()
        {
            if(container_)
            {
                switch(type_)
                {
                    case MESH_VEC4: delete ((Vec4Container*)container_); break;
                    case MESH_VEC3: delete ((Vec3Container*)container_); break;
                    case MESH_FLOAT: delete ((FloatContainer*)container_); break;
                    default: break;
                }
            }
        }

        void updateAttributeValues(M& mesh)
        {
            updateFun_(*this, mesh);
        }

        void* container(){return container_;}
        bool operator<(const AttributeHandle& a){return strcmp(name_.c_str(), a.name_.c_str()) < 0;}

        std::string& name(){return name_;}
        size_t size() const {return size_;}
        AttributeType type() const {return type_;}
        AttributeHost host() const {return host_;}

        UnsafeView unsafeView() const
        {
            UnsafeView view;
            switch(type_)
            {
                case MESH_VEC4: view = unsafeViewOfContainer(*getAttributeArrayVec4()); break;
                case MESH_VEC3: view = unsafeViewOfContainer(*getAttributeArrayVec3()); break;
                case MESH_FLOAT: view = unsafeViewOfContainer(*getAttributeArrayFloat()); break;
            }
            return view;
        }

        AnnotatedView annotatedView() const
        {
            return AnnotatedView(name_, host_, type_, unsafeView());
        }

    private:
        std::string    name_;
        AttributeHost  host_;
        AttributeType  type_;
        UpdateFun updateFun_;

        void*  container_;
        size_t size_;
        bool   attached_;

    };



}; // class Mesh ends

/**
  Directed Edge Mesh:
vertex: position, outgoing_edge_index
edge: start_vertex_index, facing_edge_index
Face f_i: e_i:valence * f_i + 0, + 1, + 2 , ( + 3)

Face ordering: counter-clockwise.
For triangles or quads (face valence of three or four)
*/
template<int VALENCE>
class ConstantValenceMesh
{
public:

    typedef vec3 vertex_type;
    typedef Mesh::AttributeHandle<ConstantValenceMesh<VALENCE>> Attribute;


    typedef int32_t edge_index;
    typedef int32_t vertex_index;
    // Now we can fit two face indices to a single 64-btye
    /** Total size must be aligned to 16 bytes to use sse through Eigen effectively.
     * Now size is 3 * 4 bytes + 4 bytes = 16 bytes. */

    static const edge_index edge_index_nil = -1; // Signifies that there is no edge
    // values smaller than this are an index to the auxiliary list.

    /** Check if the index signifies data is stored in the auxiliary table. */
    inline static edge_index auxiliaryIndex(edge_index i){return -(i) - 1;}
    inline static bool       isAuxiliary(edge_index i){return i < nil;}

    /** Position information is married to the out edge data. */
    struct Vertex
    {
        vertex_type position;
        edge_index  edgeOut;

        Vertex(float x, float y, float z)
        {
            position = vertex_type(x, y, z);
            edgeOut = edge_index_nil;
        }
    };

    struct Edge
    {
        vertex_index end;
        edge_index   opposite;

        Edge(vertex_index endi, edge_index oppositei = edge_index_nil):end(endi), opposite(oppositei){}
    };

    typedef std::vector<Vertex>  VertexArray;
    typedef std::vector<Edge>    EdgeArray;
    // TODO: implement an unassignable collection for these types of things
    // So we can safely return a reference to it.

    VertexArray vertices_;
    EdgeArray   edges_;

    Vertex& vertex(vertex_index i){return vertices_[i];}
    Edge& edge(edge_index i){return edges_[i];}

    /** An auxiliary edge table contains edge data for non-manifold vertices. Entries
     *  are formatted so first entry n contains the number of edges and the  next n entries
     *  are edge indices.
     *  |3 e_m e_n e_o | 4 e_a e_b e_c e_d| ...
     */
    std::vector<edge_index> auxiliaries_; 

    typedef std::set<Attribute> AttributeContainer;

    AttributeContainer attributes_;
    std::string        positionName_; //> Position name is handled separately due to implementation
    Attribute          vertexAdapter_; //> return a pointer in collections returning attributes

    /** TriMesh constructor */
    ConstantValenceMesh(const AttributeContainer& attributes):positionName_(GR_POSITION), vertexAdapter_(positionName_, sizeof(Vertex), &vertices_[0])
    {
        // First assert that there are no duplicate names
        if(find(attributes, positionName_))
        {
            assert(!"Duplicate names");
            throw  std::runtime_error("Tri Mesh: duplicate names in assignment. Do not use position name as a free attribute name.");
        }

        attributes_ = attributes;

        auto registerAttribute = [&](const AttributeV& attr)
        {
            attr.attach();
        };

        forEach(attributes_, registerAttribute);
    }

    AttributeContainer& attributes(){return attributes_;}

    size_t vertexCount(){return vertices_.size();}
    size_t edgeCount(){return edges_.size();}
    size_t faceCount(){return edges_.size() / VALENCE;}

    VertexArray& vertices(){return vertices_;}
    EdgeArray& edges(){return edges_;}

    void resizeAttributeArrays()
    {
        size_t size = vertices_.size();
        forEach(attributes_, std::mem_fun_ref(std::bind(&Attribute.resizeTo, size)));
    }
    
    void updateAttributeValues()
    {
        forEach(attributes_, std::mem_fun_ref(&Attribute.updateAttributeValues));
    }

    void updateAttributes()
    {
        resizeAttributeArrays();
        updateAttributeValues();
    } 

    struct VertexHandle
    {
        vertex_index index;
    };
    
    typedef std::vector<VertexHandle> VertexHandleArray;

    VertexHandle addVertex(float x, float y, float z)
    {
        VertexHandle r;
        r.index = size(vertices_);
        vertices_.push_back(Vertex(x, y, z));
        return r;
    }

    edge_index addEdge(vertex_index v)
    {
        edge_index opposingEdge = vertices_[v].edgeOut;
        edges_.push_back(Edge(v, opposingEdge));
        return lastIndex(edges_);
    }

    void addFaces(VertexHandleArray& vertexHandles)
    {
        int newFaceCount = size(vertices) / VALENCE;

        auto pushEdge = [&](vertex_index f)
        {
            vertex_index last = f + VALENCE;
            for(auto v = f; v < last; ++v)
            {
                auto nextVertex = (v + 1)%VALENCE;
                auto edge_i = addEdge(nextVertex);
                vertices[v].edgeOut = edge_i;
            }
        };

        for(int f = 0; f < newFaceCount; ++f)
        {
            pushEdge(f);
        }
    }

    /** Return a const reference to the identifier of position channel*/
    std::string& positionName() {return positionName_;}
    /** Explicit setter for position name.*/
    void setPositionName(const char* str){positionName_ = str;}

    UnsafeView vertexPositionUnsafeView()
    {
        size_t offsetSize = sizeof(Vertex); 
        size_t sampleSize = sizeof(vertex_type);
        size_t size = vertices_.size();
        return UnsafeView((uint8_t*)(&vertices_[0]), offsetSize, sampleSize, size);
    }

    class AttributeView
    {
    public:

        class iterator
        {
            void getNextAttr()
            {
                while(++attributeIterator_ != attributeIteratorEnd_)
                {
                    if(attributeIterator_->host() == hostType_)
                    {
                        parent_.currentView_ = attributeIterator_->annotatedView();
                        break;
                    }
                }
            }

        public:
            /** Attach iterator to host */
            iterator(typename AttributeContainer::iterator iter, typename AttributeContainer::iterator endIter, AttributeView& parent):
            attributeIterator_(iter), attributeIteratorEnd_(endIter),parent_(parent), pastPosition_(false), hostType_(parent_.hostType())
            {
                if(hostType_ == Mesh::VERTEX)
                {
                    // First assign position. Then go through iterators.
                    parent_.currentView() =  Mesh::annotatedView(parent_.parent().positionName(), Mesh::VERTEX, Mesh::MESH_VEC3, parent_.parent().vertexPositionUnsafeView());
                }
                else
                {
                    pastPosition_ = true;
                    if(attributeIterator_ != attributeIteratorEnd_)
                    {
                        if(attributeIterator_->host() == hostType_)
                        {
                            
                            parent_.currentView_ = attributeIterator_->annotatedView();
                        }
                        else
                        {
                            getNextAttr();
                        }
                    }
                }
            }

            Mesh::AnnotatedView& operator*(){return parent_.currentView();}

            /** Move current view to next element. */
            void operator++()
            {
               pastPosition_ = true;
               getNextAttr();
            }

            bool operator!=(const iterator& i)
            {
                return pastPosition_ != i.pastPosition_ || attributeIterator_ != i.attributeIterator_;
            }

        private:
            bool                pastPosition_;
            AttributeView&      parent_;
            Mesh::AttributeHost hostType_;
            typename AttributeContainer::iterator attributeIterator_;
            typename AttributeContainer::iterator attributeIteratorEnd_;
       };

        AttributeView(ConstantValenceMesh& parent, Mesh::AttributeHost hostType):parent_(parent), hostType_(hostType)
        {
        }

        ConstantValenceMesh& parent(){return parent_;}
        Mesh::AnnotatedView&    currentView(){return currentView_;}
        Mesh::AttributeHost&    hostType(){return hostType_;}

        /** The iterators use the internal variables of the instance of this class to keep state. As such, when the instance
         * of this class goes out of scope the iterators are no longer defined. Which might have been expected.*/
        iterator begin()
        {
            return iterator(parent_.attributes().begin(), parent_.attributes().end(), *this);
        }

        iterator end()
        {
            return iterator(parent_.attributes().end(), parent_.attributes().end(), *this);
        }

        Mesh::AnnotatedView  currentView_;
    private:
        Mesh::AttributeHost  hostType_;

        ConstantValenceMesh& parent_;
    };

    AttributeView getAttributeView(Mesh::AttributeHost hostType)
    {
        return AttributeView(*this, hostType);
    }

    /* Edge functions */
    /** Return index to first edge of face. */
    static edge_index edgeOfFace(edge_index i){return i*VALENCE;}

    /** Return index to next edge of face. */
    static edge_index nextEdge(edge_index e)
    {
        return (e%VALENCE == (VALENCE - 1)) ? e - VALENCE + 1 : e + 1;
    }

    /** Return index to prev edge of face*/
    static edge_index prevEdge(edge_index e)
    {
        return (e%VALENCE == 0) ? e + VALENCE - 1 : e - 1;
    }
    
    /** Return index of start vertex of edge. */
    vertex_index edgeStart(edge_index i)
    {
        return edges_[prevEdge(i)].end;
    }

    /** Return index of end vertex of edge. */
    vertex_index edgeEnd(edge_index i)
    {
        return edges_[i].end;
    }

    /*
     * streamdata s;
     * mesh m;
     * face_iterator face = m.facesBegin();
     * face_iterator end = m.facesEnd();
     * indexAdder inserter= s.indexInserter(TRIANGLES);
     * for(; face != end; ++face)
     * {
     *      vertexindex v0, v1, v2;
     *      auto e = face.firstEdge();
     *      v0 = m.edgeStart(*e); e++;
     *      v1 = m.edgeStart(*e); e++;
     *      v2 = m.edgeStart(*e);
     *      inserter.pushIndices(v0, v1, v2);
     *
     * }
     * */

    class edge_iterator
    {
     public:
        edge_iterator(edge_index i):i_(i){}
        edge_index operator*(){return i_;}
        void operator++(){i_ = nextEdge(i_);}
    private:
        edge_index i_;
    };

    class face_iterator
    {
    public:
        face_iterator(size_t index):index_(index){}
        void operator++(){index_ += VALENCE;}
        edge_iterator firstEdge(){return edge_iterator(index_);}
        bool operator!=(const face_iterator& i)
        {
            return index_ != i.index_;
        }
    private:
        edge_index index_;
    };

    face_iterator facesBegin(){return face_iterator(0);}
    face_iterator facesEnd(){return face_iterator(faceCount());}

};

typedef ConstantValenceMesh<3> TriangleMesh;
typedef ConstantValenceMesh<4> QuadMesh;

bool mapToStream(StreamDataComposition& streamData, Mesh::AnnotatedView& view)
{
    StreamSignature& signature(streamData.signature()); 
    bool result = false;
    bool hasChannel = signature.offsetOfChannel(view.name) != nullptr;
    bool compatibleType = signature.sampleSize() == view.view.sampleSize();
    if(hasChannel && compatibleType)
    {
        result = streamData.insert(view.name, view.view);
    }
    return result;
}

/** Transfer mesh data to vertex buffers stored in streamData. */
void triangleMeshToStreamData(TriangleMesh& mesh, StreamDataComposition& streamData)
{

    // collect vertices
    auto vertexAttributes = mesh.getAttributeView(Mesh::VERTEX);
    auto end = vertexAttributes.end();
    for(auto i = vertexAttributes.begin(); i != end; ++i)
    {
        bool result = mapToStream(streamData, *i);
        if(!result)
        {
                assert(!"could not map attribute to stream data.");
        }
    }
}

}// namespace gr
